import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import wei.yigulu.modbus.domain.ModbusSlaveDataContainer;
import wei.yigulu.modbus.domain.datatype.ModbusDataTypeEnum;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 协议采集到数据的容器
 *
 * @author 修唯xiuwei
 * @version 3.0
 */

@Slf4j
public class ProtocolDataContainer {


    /**
     * 遥测数据
     */
    private Map<Integer, BigDecimal> yc = new ConcurrentHashMap<>();

    /**
     * 遥信数据
     */
    private Map<Integer, Boolean> yx = new ConcurrentHashMap<>();
    /**
     * 点位数据是否是有效值
     */
    private Map<Integer, Boolean> isValid = new ConcurrentHashMap<>();

    /**
     * 点位计算器
     */
    private Map<Integer, PointDataCalculator> calculators = new ConcurrentHashMap<>();

    /**
     * 点位计算器的线程任务
     */
    private Map<Integer, Callable> calculatorTasks = new ConcurrentHashMap<>();

    private ProtocolDataContainer() {
    }

    /**
     * 获取单例实例
     *
     * @return the instance
     */
    public static final ProtocolDataContainer getInstance() {
        return LazyHolder.INSTANCE;
    }

    /**
     * 向池子内置数据
     *
     * @param point point
     * @param value value
     */
    public void putNumber(Integer point, BigDecimal value) {
        this.yc.put(point, value);
    }

    public Map<Integer, Number> getNumbers() {
        Map<Integer, Number> numberMap = new HashMap<>();
        this.yc.forEach((k, v) -> numberMap.put(k, v));
        return numberMap;
    }

    public Map<Integer, Boolean> getBooleans() {
        Map<Integer, Boolean> booleanMap = new HashMap<>();
        this.yx.forEach((k, v) -> booleanMap.put(k, v));
        return booleanMap;
    }

    /**
     * 向池子内置数据
     *
     * @param point point
     * @param value value
     */
    public void putBoolean(Integer point, Boolean value) {
        this.yx.put(point, value);
    }

    /**
     * 设定点位是否可用
     *
     * @param point point
     * @param value value
     */
    public void setAvailable(Integer point, Boolean value) {
        this.isValid.put(point, value);
    }

    /**
     * 获取到缓存池内的数值 如果遥测池内没有但是存在于遥信池 将返回 0or1
     *
     * @param point 点位
     * @return Number number
     */
    public BigDecimal getNumber(Integer point) {
        if (isYcContains(point)) {
            return this.yc.get(point);
        } else if (isYxContains(point)) {
            return this.yx.get(point) ? BigDecimal.valueOf(1D) : BigDecimal.valueOf(0D);
        } else {
            return BigDecimal.valueOf(-99D);
        }
    }

    /**
     * 获取到返回池内的遥信数据  如果遥信池内没有 遥测池内有 那么大于0的数值 将会返回ture 否则为false
     *
     * @param point 点位
     * @return Boolean boolean
     */
    public Boolean getBoolean(Integer point) {
        if (isYxContains(point)) {
            return this.yx.get(point);
        } else if (isYcContains(point)) {
            return this.yc.get(point).compareTo(BigDecimal.ZERO) > 0;
        } else {
            return false;
        }
    }

    /**
     * 获取点位数据  直接返回
     *
     * @param point 点位key
     * @return 返回值  可能是Boolean 或者是 BigDecimal
     */
    public Object getData(Integer point) {
        if (isYcContains(point)) {
            return this.yc.get(point);
        }
        if (isYxContains(point)) {
            return this.yx.get(point);
        }
        return BigDecimal.valueOf(0);
    }





    public void deleteData(Integer point) {
        if (isYcContains(point)) {
            this.yc.remove(point);
        }
        if (isYxContains(point)) {
            this.yx.remove(point);
        }

    }


    /**
     * 查看某点位数据是否可用
     *
     * @param point point
     * @return the available
     */
    public Boolean getAvailable(Integer point) {
        return this.isValid.get(point);
    }

    /**
     * 遥信数据池是否含有该点位
     *
     * @param point point
     * @return the boolean
     */
    public Boolean isYxContains(Integer point) {
        return this.yx.containsKey(point);
    }

    /**
     * 遥测数据池是否含有该点位值
     *
     * @param point point
     * @return the boolean
     */
    public Boolean isYcContains(Integer point) {
        return this.yc.containsKey(point);
    }

    /**
     * 向计算器池子中  添加计算器
     *
     * @param point      点位id
     * @param calculator 点位计算器
     * @return 点位计算器
     */
    public PointDataCalculator addCalculator(Integer point, PointDataCalculator calculator) {
        removeCalculatorTask(point);
        this.calculators.put(point, calculator);
        this.calculatorTasks.put(point, RecurringTaskContainer.getInstance().addRecurringTask(5, "计算器计算任务", () -> {
            Object obj = null;
            try {
                obj = calculator.calculate();
                if (obj != null) {
                    if (obj instanceof Boolean) {
                        this.putBoolean(point, (Boolean) obj);
                    } else {
                        this.putNumber(point, new BigDecimal(obj.toString()));
                    }
                } else {
                    log.warn("计算公式{}结果值为空", calculator.getValue());
                }
            } catch (Exception ex) {
                log.error("数据计算器池计算异常", ex);
            }
            return obj;
        }));
        return calculator;
    }


    /**
     * 移除点位计算线程任务
     *
     * @param point 点位id
     */
    public void removeCalculatorTask(Integer point) {
        if (this.calculatorTasks.containsKey(point)) {
            RecurringTaskContainer.getInstance().removeTask(this.calculatorTasks.get(point));
            this.calculatorTasks.remove(point);
        }
    }


    /**
     * 移除所有的点位计算线程任务
     */
    public void removeAllCalculatorTask() {
        this.calculatorTasks.forEach((k, v) -> RecurringTaskContainer.getInstance().removeTask(v));
        this.calculatorTasks.clear();
    }

    /**
     * 添加计算器
     * 向计算器池子中  添加计算器
     *
     * @return {@link PointDataCalculator}
     */
    public PointDataCalculator addCalculator(PointDataCalculator pointDataCalculator) {
        return addCalculator(pointDataCalculator.getPoint(), pointDataCalculator);
    }


    @Override
    public String toString() {
        JSONObject object = new JSONObject();
        object.put("yx", this.yx);
        object.put("yc", this.yc);
        return object.toJSONString();
    }

    public JSONObject getValues() {
        JSONObject jsonObject = new JSONObject();
        this.yc.forEach((k, v) ->{
            BasePointData pointData=PointDataContainer.getInstance().getDataPoint(k);
                if(pointData!=null && pointData.getDataType().name().contains("_")){
                    jsonObject.put(k.toString(), v.intValue());
                }else{
                    jsonObject.put(k.toString(), v.setScale(2,RoundingMode.HALF_UP));
                }});
        this.yx.forEach((k, v) -> jsonObject.put(k.toString(), v));
        return jsonObject;
    }


    private static class LazyHolder {
        private static final ProtocolDataContainer INSTANCE = new ProtocolDataContainer();
    }


}
